package zl0.marshalling;

import java.io.StringReader;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Stack;

import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

public class Unmarshal {
	public static Object unmarshal(String xml) throws Exception {
        if(xml == null) {
            return null;
        }
        final Stack<Object> stackObject = new Stack<Object>();
        final Stack<String> stackString = new Stack<String>();
        final Map<String, Class<?>> clases = new HashMap<String, Class<?>>();
        final Map<String, String> atributos = new HashMap<String, String>();
        SAXParserFactory.newInstance().newSAXParser().parse(new InputSource(new StringReader(xml)), new DefaultHandler() {
            public void startElement(String uri, String localName, String qName, Attributes attributes) {
                try {
                    if("array".equals(qName)) {
                        stackObject.push(Array.newInstance(Class.forName(attributes.getValue("class")), Integer.valueOf((attributes.getValue("lenght"))).intValue()));
                        stackString.push(qName);
                    } else if(qName.startsWith("set")) {
                        final Object obj = stackObject.peek();
                        final String clase = attributes.getValue("class");
                        final String tipo = attributes.getValue("type");
                        final String valor = attributes.getValue("value");
                        if(clase == null) {
                            final Class<?> clasz = Class.forName(tipo);
                            final Method method = obj.getClass().getMethod(qName, clasz);
                            if(Date.class.getName().equals(tipo)) {
                                method.invoke(obj, new Date(Long.parseLong(valor)));
                            } else if(Character.class.getName().equals(tipo)) {
                                method.invoke(obj, Character.valueOf(valor.charAt(0)));
                            } else {
                                method.invoke(obj, clasz.getConstructor(String.class).newInstance(valor));
                            }
                        } else {
                            final Class<?> clasz = Class.forName(clase);
                            stackObject.push(clasz.newInstance());
                            obj.getClass().getMethod(qName, clasz).invoke(obj, stackObject.peek());
                            stackString.push(qName);
                        }
                    } else {
                        final Object obj;
                        final Class<?> clasz = Class.forName(qName);
                        if(Collection.class.isAssignableFrom(clasz)) {
                            obj = clasz.getConstructor(Integer.TYPE).newInstance(Integer.valueOf((attributes.getValue("size"))));
                        } else {
                            obj = clasz.newInstance();
                        }
                        if(!stackObject.empty()) {
                            final Object peek = stackObject.peek();
                            if(peek instanceof Collection) {
                                ((Collection)peek).add(obj);
                            } else if (peek.getClass().isArray()) {
                                Array.set(peek, Integer.valueOf((attributes.getValue("index"))).intValue(), obj);
                            }
                        }
                        stackObject.push(obj);
                        stackString.push(qName);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            public void endElement (String uri, String localName, String qName) throws SAXException {
                if(qName.equals(stackString.peek())) {
                    stackString.pop();
                    if(!stackString.isEmpty()) {
                        stackObject.pop();
                    }
                }
            }
        });
        return stackObject.pop();
	}
	
	public static Object unmarshalOld(String xml) throws Exception {
        if(xml == null) {
            return null;
        }
        final Stack<Object> stackObject = new Stack<Object>();
        final Stack<String> stackString = new Stack<String>();
        SAXParserFactory.newInstance().newSAXParser().parse(new InputSource(new StringReader(xml)), new DefaultHandler() {
            @SuppressWarnings("unchecked")
            public void startElement(String uri, String localName, String qName, Attributes attributes) {
                try {
                    if("array".equals(qName)) {
                        stackObject.push(Array.newInstance(Class.forName(attributes.getValue("class")), Integer.valueOf((attributes.getValue("lenght"))).intValue()));
                        stackString.push(qName);
                    } else if(qName.startsWith("set")) {
                        final Object obj = stackObject.peek();
                        final String clase = attributes.getValue("class");
                        final String tipo = attributes.getValue("type");
                        final String valor = attributes.getValue("value");
                        if(clase == null) {
                            final Class<?> clasz = Class.forName(tipo);
                            final Method method = obj.getClass().getMethod(qName, clasz);
                            if(Date.class.getName().equals(tipo)) {
                                method.invoke(obj, new Date(Long.parseLong(valor)));
                            } else if(Character.class.getName().equals(tipo)) {
                                method.invoke(obj, Character.valueOf(valor.charAt(0)));
                            } else {
                                method.invoke(obj, clasz.getConstructor(String.class).newInstance(valor));
                            }
                        } else {
                            final Class<?> clasz = Class.forName(clase);
                            stackObject.push(clasz.newInstance());
                            obj.getClass().getMethod(qName, clasz).invoke(obj, stackObject.peek());
                            stackString.push(qName);
                        }
                    } else {
                        final Object obj;
                        final Class<?> clasz = Class.forName(qName);
                        if(Collection.class.isAssignableFrom(clasz)) {
                            obj = clasz.getConstructor(Integer.TYPE).newInstance(Integer.valueOf((attributes.getValue("size"))));
                        } else {
                            obj = clasz.newInstance();
                        }
                        if(!stackObject.empty()) {
                            final Object peek = stackObject.peek();
                            if(peek instanceof Collection) {
                                ((Collection)peek).add(obj);
                            } else if (peek.getClass().isArray()) {
                                Array.set(peek, Integer.valueOf((attributes.getValue("index"))).intValue(), obj);
                            }
                        }
                        stackObject.push(obj);
                        stackString.push(qName);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            public void endElement (String uri, String localName, String qName) throws SAXException {
                if(qName.equals(stackString.peek())) {
                    stackString.pop();
                    if(!stackString.isEmpty()) {
                        stackObject.pop();
                    }
                }
            }
        });
        return stackObject.pop();
    }
}